﻿using System;
using System.IO;
using System.Xml.Serialization;
using Ionic.Zip;
using MediaSage.Lib.Client.Config;
using MediaSage.Lib.Database.Entities.Client;
using MediaSage.Lib.UIFramework.DataFile;
using MediaSage.Lib.UIFramework.Theme;
using MediaSage.Lib.Utilities;

namespace MediaSage.MCClient.Code.Themes
{
    /// <summary>
    /// Class to install/upgrade themes as well as find out information about currently installed themes
    /// </summary>
    public class ThemeInstaller
    {
        private Configuration config;
        private ClientLogger logger;
        private ThemeManager themeManager;

        public ThemeInstaller(Configuration config)
        {
            this.config = config;
            this.logger = config.Logger;
            this.themeManager = config.getThemeManager();
            logger.Log("MediaSage.MCCLient.Code.Themes.ThemeInstaller is initialized");
        }

        /// <summary>
        /// Returns true if the Theme is currently installed
        /// </summary>
        /// <param name="themeName">Unique key for the theme</param>
        /// <returns></returns>
        public bool IsThemeInstalled(string themeName)
        {
            logger.Log("Entering MediaSage.MCClient.Code.Themes.ThemeInstaller(" + themeName + ")");
            DBUtil.GetSession().Refresh(config.Themes);
            foreach (ThemeEntity te in config.Themes)
            {
                if (te.Name.Equals(themeName))
                {
                    logger.Log("Exiting MediaSage.MCClient.Code.Themes.ThemeInstaller(" + themeName + "), rc==true");
                    return (true);
                }
            }
            logger.Log("Exiting MediaSage.MCClient.Code.Themes.ThemeInstaller(" + themeName + "), rc==false");
            return (false);
        }

        /// <summary>
        /// Returns the version of the theme
        /// </summary>
        /// <param name="themeName">Unique key for the theme</param>
        /// <returns>The version struct, null if it is not installed</returns>
        public System.Version ThemeVersion(string themeName)
        {
            logger.Log("Entering MediaSage.MCClient.Code.Themes.ThemeVersion(" + themeName + ")");
            DBUtil.GetSession().Refresh(config.Themes);
            foreach (ThemeEntity te in config.Themes)
            {
                if (te.Name.Equals(themeName))
                {
                    logger.Log(String.Format("Exiting MediaSage.MCClient.Code.Themes.ThemeVersion({0}), rc={1}.{2}.{3}", te.Name, te.MajorVersion, te.MinorVersion, te.BuildNumber));
                    return (new System.Version(te.MajorVersion, te.MinorVersion, te.BuildNumber));
                }
            }
            logger.Log("Exiting MediaSage.MCClient.Code.Themes.ThemeVersion(" + themeName + "), rc==null");
            return (null);
        }

        /// <summary>
        /// Installs a theme into the theme repository
        /// </summary>
        /// <param name="themePackageFile">ZIP file containing the theme</param>
        /// <param name="force">Overwrites a currently installed theme</param>
        /// <returns></returns>
        public bool InstallTheme(string themePackageFile, bool force)
        {
            logger.Log(String.Format("Entering MediaSage.MCClient.Code.Themes.InstallTheme({0},{1})", themePackageFile, force.ToString()));

            if (!File.Exists(themePackageFile))
            {
                logger.Log(Logger.Levels.ERROR, string.Format("Theme package file: {0} does not exist", themePackageFile));
                logger.Log(String.Format("Exiting MediaSage.MCClient.Code.Themes.InstallTheme({0},{1}), rc==false", themePackageFile, force.ToString()));
                return false;
            }

            // First unzip the theme to a temp dir
            string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());

            try
            {
                Directory.CreateDirectory(tempDir);
            }
            catch (Exception e)
            {
                logger.Log(Logger.Levels.ERROR, String.Format("Exception creating temporary directory: {0}", e.Message));
                logger.Log(String.Format("Exiting MediaSage.MCClient.Code.Themes.InstallTheme({0},{1}), rc==false", themePackageFile, force.ToString()));
                return false;
            }

            try
            {
                var zip = new ZipFile(themePackageFile);
                zip.ExtractAll(tempDir);
            }
            catch (Exception e)
            {
                logger.Log(Logger.Levels.ERROR, String.Format("Exception extracting theme: {0}", e.Message));
                Directory.Delete(tempDir, true);
                logger.Log(String.Format("Exiting MediaSage.MCClient.Code.Themes.InstallTheme({0},{1}), rc==false", themePackageFile, force.ToString()));
                return false;
            }

            // Load the themedata
            ThemeData data = getThemeData(Path.Combine(tempDir, "THEME.XML"));
            if (data == null)
            {
                // We failed parsing the data
                logger.Log("Failed to retrieve data from theme package, see previous error");
                Directory.Delete(tempDir, true);
                logger.Log(String.Format("Exiting MediaSage.MCClient.Code.Themes.InstallTheme({0},{1}), rc==false", themePackageFile, force.ToString()));
                return false;
            }

            // Now lets check to see if the theme is installed
            bool installed = IsThemeInstalled(data.Name);
            if (installed && !force)
            {
                logger.Log("Theme is already installed, and force==false");
                logger.Log(String.Format("Exiting MediaSage.MCClient.Code.Themes.InstallTheme({0},{1}), rc==false", themePackageFile, force.ToString()));
                return false;
            }

            // Ok, if its installed lets remove the old one
            if (!UninstallTheme(data.Name))
            {
                logger.Log("Failed to uninstall old theme, see previous error");
                logger.Log(String.Format("Exiting MediaSage.MCClient.Code.Themes.InstallTheme({0},{1}), rc==false", themePackageFile, force.ToString()));
                return false;
            }

            // Ok, now commence with install

            // Step one, copy the goop from tempDir to our install dir
            string themeInstallDir = Path.Combine(config.getThemeManager().ThemeDirectory, data.Name);
            if (!Directory.Exists(themeInstallDir))
            {
                try
                {
                    Directory.CreateDirectory(themeInstallDir);
                }
                catch (Exception e)
                {
                    logger.Log(Logger.Levels.ERROR, String.Format("Failed to create theme directory: {0}.  Error message: {1}", themeInstallDir, e.Message));
                    logger.Log(String.Format("Exiting MediaSage.MCClient.Code.Themes.InstallTheme({0},{1}), rc==false", themePackageFile, force.ToString()));
                    return false;
                }
            }

            try
            {
                Files.CopyFolder(tempDir, themeInstallDir, true);
            }
            catch (Exception e)
            {
                logger.Log(Logger.Levels.ERROR, String.Format("Failed to copy files to new directory, error message: {0}", e.Message));
                logger.Log(String.Format("Exiting MediaSage.MCClient.Code.Themes.InstallTheme({0},{1}), rc==false", themePackageFile, force.ToString()));
                return false;
            }

            // Now create our ThemeEntity
            ThemeEntity te = new ThemeEntity();
            te.BuildNumber = data.Version.BuildNumber;
            te.MinorVersion = data.Version.Minor;
            te.MajorVersion = data.Version.Major;
            te.Location = Path.Combine(themeInstallDir, data.DLL);
            te.IsDefault = data.Default;
            foreach (ThemeCapabilities capability in data.Capabilitiy)
            {
                te.Capability.Add(capability);
            }

            te.Save();
            config.Themes.Add(te);
            if (data.Default)
            {
                foreach (ThemeEntity theme in config.Themes)
                {
                    if (theme.IsDefault && !theme.Name.Equals(te.Name))
                    {
                        theme.IsDefault = false;
                        theme.SaveOrUpdate();
                    }
                }

                // TODO: Reset our default theme in ThemeManager
            }

            config.SaveOrUpdate();

            // Now load our theme
            ThemeObject to = themeManager.LoadTheme(te.Location);
            if (to == null)
            {
                logger.Log(Logger.Levels.ERROR, "Failed to load the theme object");
                logger.Log(String.Format("Exiting MediaSage.MCClient.Code.Themes.InstallTheme({0},{1}), rc==false", themePackageFile, force.ToString()));
                return false;
            }
            logger.Log(String.Format("Exiting MediaSage.MCClient.Code.Themes.InstallTheme({0},{1}), rc==true", themePackageFile, force.ToString()));
            return true;
        }

        public bool UninstallTheme(string ThemeName)
        {
            logger.Log(String.Format("Entering MediaSage.MCClient.Code.Themes.UninstallTheme({0})", ThemeName));
            ThemeEntity theme = null;
            foreach (ThemeEntity te in config.Themes)
            {
                if (te.Name.Equals(ThemeName))
                {
                    theme = te;
                }
            }

            if (theme == null)
            {
                logger.Log(Logger.Levels.WARN, "Theme is not installed");
                logger.Log(String.Format("Exiting MediaSage.MCClient.Code.Themes.UninstallTheme({0}), rc==true", ThemeName));
                return true;
            }

            // Attempt to unload the theme, if it is indeed loaded (If its not its good too)
            config.getThemeManager().UnloadTheme(theme.Location);

            // Now delete the directory the theme lives in
            try
            {
                if (theme.Location != null)
                {
                    Directory.Delete(Path.GetDirectoryName(theme.Location));
                }
            }
            catch (Exception e)
            {
                logger.Log(Logger.Levels.ERROR, "Failed to remove the directory");
            }

            // Remove the actual theme from the DB
            DBUtil.GetSession().Delete(theme);

            // We are done
            logger.Log(String.Format("Exiting MediaSage.MCClient.Code.Themes.UninstallTheme({0}), rc==true", ThemeName));
            return true;
        }

        /// <summary>
        /// Used internally, reads the THEME.XML file and returns the data construct
        /// Null if there is an error, or its not in the directory
        /// </summary>
        /// <param name="themeDataDir"></param>
        /// <returns>ThemeData or Null</returns>
        private ThemeData getThemeData(string themeDataDir)
        {
            logger.Log(String.Format("Entering MediaSage.MCClient.Code.Themes.getThemeData({0})", themeDataDir));
            string themeXMLFile = Path.Combine(themeDataDir, "THEME.XML");
            if (!File.Exists(themeXMLFile))
            {
                logger.Log(String.Format("File: {0} does not exist", themeXMLFile));
                logger.Log(String.Format("Exiting MediaSage.MCClient.Code.Themes.getThemeData({0}), rc==null",
                                         themeDataDir));
                return null;
            }

            ThemeData data = null;
            try
            {
                XmlSerializer xmlSerializer = new XmlSerializer(typeof(ThemeData));
                TextReader tr = new StreamReader(themeXMLFile);
                data = (ThemeData)xmlSerializer.Deserialize(tr);
                tr.Close();
            }
            catch (Exception e)
            {
                logger.Log(String.Format("Exception deserializing file: {0}.  Message: {1}", themeXMLFile, e.Message));
                logger.Log(String.Format("Exiting MediaSage.MCClient.Code.Themes.getThemeData({0}), rc==null",
                                         themeDataDir));
                return null;
            }

            logger.Log(String.Format("Exiting MediaSage.MCClient.Code.Themes.getThemeData({0}), rc==ThemeData structure",
                                     themeDataDir));
            return data;
        }
    }
}